﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  Monitor.cs
//    
// Abstract:
//
//  This module implements the Monitor class. This class monitors changes to a given user's mailbox. The changes of interest 
//  are specified as a search expression. The search expression is used to create a search folder. The monitor subscribes to 
//  notifications from EWS for the reuslting search folder. The monitor has a unique ID which is how a given EWS notification 
//  is routed to the coorrect monitor. When the search expression changes (e.g. new rules are fetched), the old search folder
//  is deleted before a new search folder is created using the new search expression. The first time that a monitor is 
//  instantiated it checks for and deletes any pre-existing seearch folders (perhaps orphaned by a previous execution of the
//  Scheduler service).
//
//--

using System;
using System.Diagnostics;
using System.Threading;
using Microsoft.OfficeCommunicationsServer.Applications.Common;

namespace Microsoft.EnhancedReminders.Scheduler
{
    /// <summary>
    /// Monitors changes to a given mailbox that match a specified search expression.
    /// </summary>
    internal abstract class Monitor : IDisposable
    {
        // Frequency of status notifications from EWS
        internal static int SubscriptionStatusFrequency = Helpers.GetSetting<int>(Constants.SubscriptionStatusFrequencyInMinutesSetting, 1);

        // Subscription timeout. The timeout is twice the expected notification status frequency.
        internal static int SubscriptionTimeout = SubscriptionStatusFrequency * 60 * 1000 * 2;

        // WCF proxy for communicating with EWS
        protected readonly ExchangeServicePortType _client;

        // Impersonation details for this user
        protected readonly ExchangeImpersonationType _impersonation;

        // Mailbox address for this user
        protected readonly string _emailAddress;

        // Name of the search folder
        private readonly string _name;

        // The search folder
        private BaseFolderIdType _searchFolderId;

        // The parent of the search folder
        private readonly BaseFolderIdType _parentFolderId;

        // The target folder for the search folder
        private readonly BaseFolderIdType _targetFolderId;

        // Type of traversal used by the search folder
        private readonly SearchFolderTraversalType _traversal;

        // Events this monitor is interested in
        private readonly NotificationEventTypeType[] _eventTypes;

        // Serializes handling of notification updates and other events
        protected readonly TaskSerializer _taskSerializer;

        // Subscription ID of the latest subscription request. In order to receive notification about changes to a user's 
        // mailbox, the Scheduler needs to subscribe for notifications from Exchange via EWS. It is possible to have more 
        // than one subscription active, so this ID keeps track of the most up-to-date subscription request and is used to 
        // ignore updates from earlier subscriptions. This ensures that updates are always applied in the correct order.
        // The subscription ID also identifies which monitor a given EWS notification is for.
        private Guid _subscriptionId;

        // Watermark associated with the last notification or status update. Each notification from EWS has a unique watermark
        // associated with it. It also has the watermark from the previous notification. Keeping track of the watermark can
        // therefore show when notifications have been missed. When a notification has been missed, the watermark is used to
        // re-subscribe for notifications from the last known update.
        private string _watermark;

        // Timer used to check subscription is still alive
        private Timer _subscriptionTimer;

        // Synchronizes subscription requests and notifications from EWS. Subscription requests happen on the serialized thread, but
        // notifications can arrive on any thread so synchronization is needed.
        private readonly object _syncRoot = new object();

        /// <summary>
        /// ctor.
        /// </summary>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">Mailbox account to be monitored</param>
        /// <param name="name">Name of the search folder</param>
        /// <param name="parentFolderId">Parent of the search folder</param>
        /// <param name="targetFolderId">Target of the search folder</param>
        /// <param name="traversal">Type of traversal used by the search folder</param>
        /// <param name="eventTypes">Events to subscribe to</param>
        /// <param name="taskSerializer">Task serializer</param>
        protected Monitor(ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress,
            string name, BaseFolderIdType parentFolderId, BaseFolderIdType targetFolderId, SearchFolderTraversalType traversal, 
            NotificationEventTypeType[] eventTypes, TaskSerializer taskSerializer)
        {
            Debug.Assert(client != null, "client != null");
            Debug.Assert(!String.IsNullOrEmpty(emailAddress), "!String.IsNullOrEmpty(emailAddress)");
            Debug.Assert(!String.IsNullOrEmpty(name), "!String.IsNullOrEmpty(name)");
            Debug.Assert(parentFolderId != null, "parentFolder != null");
            Debug.Assert(targetFolderId != null, "targetFolderId != null");
            Debug.Assert(eventTypes != null, "eventTypes != null");
            Debug.Assert(taskSerializer != null, "taskSerializer != null");

            _client = client;
            _impersonation = impersonation;
            _emailAddress = emailAddress;
            _name = name;
            _parentFolderId = parentFolderId;
            _targetFolderId = targetFolderId;
            _traversal = traversal;
            _eventTypes = eventTypes;
            _taskSerializer = taskSerializer;
            _subscriptionId = Guid.NewGuid();
            _subscriptionTimer = new Timer(new TimerCallback(SubscriptionTimerCallback));
        }

        /// <summary>
        /// Starts the process of updating the monitor with the specified search expression.
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="searchExpression">Search expression</param>
        /// <param name="subscriptionId">ID to be associated with the search folder subscription</param>
        /// <param name="doNext">Indicates if the resulting update task should be the next task to be executed</param>
        /// <remarks>
        /// This method has to be called on the task serializer thread because it accesses the search folder field.
        /// </remarks>
        internal void Update(TaskContext taskContext, SearchExpressionType searchExpression, Guid subscriptionId, bool doNext)
        {
            Debug.Assert(taskContext != null, "taskContext != null");
            Debug.Assert(taskContext.Context != null, "taskContext.Context != null");
            Debug.Assert(subscriptionId != _subscriptionId, "subscriptionId != _subscriptionId");

            Logger.WriteTraceVerbose(taskContext.Context, "Monitor.Update");

            // The lock synchronizes access to the fields below with notifications from EWS, which aren't executed 
            // on the task serializer
            lock (_syncRoot)
            {
                _subscriptionId = subscriptionId;
                _watermark = null;
            }

            // Stop the subscription timer so that re-subscriptions are not requested until the update completes
            StopSubscriptionTimer();

            UpdateMonitorTask updateMonitorTask = new UpdateMonitorTask(taskContext, _client, _impersonation, _emailAddress, 
                searchExpression, _searchFolderId, _parentFolderId, _targetFolderId, _traversal, _eventTypes, _name, _subscriptionId);
            updateMonitorTask.Completed += UpdateMonitorTask_Completed;
            _taskSerializer.Execute(updateMonitorTask, doNext);
        }

        /// <summary>
        /// Updates the subscription ID and watermark associated with this monitor. This will indirectly force a re-subcription with EWS.
        /// </summary>
        /// <param name="context">Logging context</param>
        /// <param name="subscriptionId">New subscription ID</param>
        /// <param name="watermark">New watermark</param>
        /// <remarks>
        /// This method is called whenever there is a transient error accessing this user's mailbox. This method resets the watermark to 
        /// the value when the error occurred. It also assigns a new subscription ID to this monitor. This ensures that new notifications 
        /// from EWS will be rejected and eventually the monitor will re-subscribe for updates from the point at which the error occurred.
        /// If this re-subscription fails, the monitor will periodically retry until it is successful.
        /// </remarks>
        internal void Resubscribe(Context context, Guid subscriptionId, string watermark)
        {
            Debug.Assert(context != null, "context");
            Debug.Assert(!String.IsNullOrEmpty(watermark), "!String.IsNullOrEmpty(watermark)");
            Debug.Assert(subscriptionId != _subscriptionId, "subscriptionId != _subscriptionId");

            Logger.WriteTraceVerbose(context, "Monitor.Resubscribe");

            // The lock synchronizes access to the fields below with notifications from EWS, which aren't executed 
            // on the task serializer
            lock (_syncRoot)
            {
                _subscriptionId = subscriptionId;
                _watermark = watermark;
            }
        }

        /// <summary>
        /// Deletes the search folder associated with this monitor.
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <remarks>
        /// This method has to be called on the task serializer thread because it accesses the search folder field.
        /// </remarks>
        internal void Shutdown(TaskContext taskContext)
        {
            Debug.Assert(taskContext != null, "taskContext != null");
            Debug.Assert(taskContext.Context != null, "taskContext.Context != null");

            Logger.WriteTraceVerbose(taskContext.Context, "Monitor.Shutdown");

            if (_searchFolderId == null)
            {
                // If there's no search folder, then shutdown is complete
                RaiseShutdownCompleted(new SchedulerEventArgs(taskContext.Context));
                return;
            }

            DeleteFolderTask deleteSearchFolderTask = new DeleteFolderTask(taskContext, _client, _impersonation, 
                _emailAddress, _searchFolderId);
            deleteSearchFolderTask.Completed += DeleteSearchFolderTask_Completed;
            _taskSerializer.Execute(deleteSearchFolderTask, true);
        }

        /// <summary>
        /// Disposes of the timer.
        /// </summary>
        public void Dispose()
        {
            lock (_syncRoot)
            {
                if (_subscriptionTimer != null)
                {
                    _subscriptionTimer.Dispose();
                    _subscriptionTimer = null;
                }
            }
        }

        /// <summary>
        /// Checks that this update from EWS is valid.
        /// </summary>
        /// <param name="context">Logging context</param>
        /// <param name="watermark">Watermark for this notification</param>
        /// <param name="previousWatermark">Previous watermark for this notification</param>
        /// <param name="subscriptionId">Subscription ID associated with this update notification</param>
        /// <returns>
        /// true if the update has the correct watermarks and subscription ID; false otherwise.
        /// </returns>
        internal bool IsValidUpdate(Context context, string watermark, string previousWatermark, string subscriptionId)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!String.IsNullOrEmpty(watermark), "!String.IsNullOrEmpty(watermark)");
            Debug.Assert(!String.IsNullOrEmpty(subscriptionId), "!String.IsNullOrEmpty(subscriptionId)");

            // The lock is necessary to synchronize synchronization requests with notifications from EWS
            lock (_syncRoot)
            {
                if (_subscriptionId == null || _subscriptionId.ToString() != subscriptionId)
                {
                    // This update notification is out of date
                    return false;
                }

                if (_watermark != null &&               // Check if this is the first update
                    _watermark != watermark &&          // Check if this is the same update as the previous one
                    previousWatermark != _watermark)    // Check if any have been missed
                {
                    // Must have missed some notifications, so need to cancel this subscription. The subscription timer will 
                    // trigger a re-subscription from the current watermark.
                    Logger.WriteTraceWarning(context, "Unexpected watermark. Watermark:{0}, Previous Watermark: {1}", watermark, previousWatermark);
                    return false;
                }
                else
                {
                    _watermark = watermark;
                }
            }

            // Reset subscription timer
            StartSubscriptionTimer();

            return true;
        }

        /// <summary>
        /// Handles new items of interest. This could be a reminder or the storage item or something matching a rule.
        /// </summary>
        /// <param name="context">Logging context</param>
        /// <param name="change">Description of changed item</param>
        /// <param name="previousWatermark">Previous watermark from the previous update</param>
        /// <param name="subscriptionId">Subscription ID associated with this notification</param>
        /// <returns>
        /// true if the update is successful; false otherwise
        /// </returns>
        internal bool OnItemCreated(ServerContext context, BaseObjectChangedEventType change, string previousWatermark, string subscriptionId)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(change != null, "change != null");
            Debug.Assert(!String.IsNullOrEmpty(subscriptionId), "!String.IsNullOrEmpty(subscriptionId)");

            if (!IsValidUpdate(context, change.Watermark, previousWatermark, subscriptionId))
            {
                // Unexpected or out-of-date notification
                return false;
            }

            GetItem(new ExchangeTaskContext(context, _subscriptionId, previousWatermark), change.Item.AssertCast<ItemIdType>());

            return true;
        }

        /// <summary>
        /// Handles modifications to items of interest. This could be a reminder or the storage item or something matching a rule.
        /// </summary>
        /// <param name="context">Logging context</param>
        /// <param name="change">Description of changed item</param>
        /// <param name="previousWatermark">Previous watermark from the previous update</param>
        /// <param name="subscriptionId">Subscription ID associated with this notification</param>
        /// <returns>
        /// true if the update is successful; false otherwise
        /// </returns>
        internal virtual bool OnItemModified(ServerContext context, BaseObjectChangedEventType change, string previousWatermark, string subscriptionId)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(change != null, "change != null");
            Debug.Assert(!String.IsNullOrEmpty(subscriptionId), "!String.IsNullOrEmpty(subscriptionId)");

            if (!IsValidUpdate(context, change.Watermark, previousWatermark, subscriptionId))
            {
                // Unexpected or out-of-date notification
                return false;
            }

            GetItem(new ExchangeTaskContext(context, _subscriptionId, previousWatermark), change.Item.AssertCast<ItemIdType>());

            return true;
        }

        /// <summary>
        /// Handles deletion of items of interest. This could be a reminder or the storage item or something matching a rule.
        /// </summary>
        /// <param name="context">Logging context</param>
        /// <param name="change">Description of changed item</param>
        /// <param name="previousWatermark">Previous watermark from the previous update</param>
        /// <param name="subscriptionId">Subscription ID associated with this notification</param>
        /// <returns>
        /// true if the update is successful; false otherwise
        /// </returns>
        internal virtual bool OnItemDeleted(ServerContext context, BaseObjectChangedEventType change, string previousWatermark, string subscriptionId)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(change != null, "change != null");
            Debug.Assert(!String.IsNullOrEmpty(subscriptionId), "!String.IsNullOrEmpty(subscriptionId)");

            if (!IsValidUpdate(context, change.Watermark, previousWatermark, subscriptionId))
            {
                // Unexpected or out-of-date notification
                return false;
            }

            OnItemDeleted(context, change.Item.AssertCast<ItemIdType>());

            return true;
        }

        /// <summary>
        /// Handle status updates for the subscriptions associated with this mailbox.
        /// </summary>
        /// <param name="context">Logging context</param>
        /// <param name="watermark">Watermark for this notification</param>
        /// <param name="previousWatermark">Previous watermark for this notification</param>
        /// <param name="subscriptionId">Subscription ID associated with this update notification</param>
        /// <returns>
        /// true if the update is successful; false otherwise
        /// </returns>
        internal bool OnStatusUpdated(ServerContext context, string watermark, string previousWatermark, string subscriptionId)
        {
            Debug.Assert(context != null, "context != null");

            return IsValidUpdate(context, watermark, previousWatermark, subscriptionId);
        }

        /// <summary>
        /// Returns the subscription ID associated with this monitor.
        /// </summary>
        internal Guid SubscriptionId
        {
            get
            {
                return _subscriptionId;
            }
        }

        /// <summary>
        /// Raised when a mailbox item associated with this monitor is retrieved.
        /// </summary>
        internal event EventHandler<TaskCompletedEventArgs> ItemRetrieved;

        /// <summary>
        /// Raised when a mailbox item associated with this monitor is deleted.
        /// </summary>
        internal event EventHandler<MonitorItemDeletedEventArgs> ItemDeleted;
        
        /// <summary>
        /// Raised when updating completes.
        /// </summary>
        internal event EventHandler<TaskCompletedEventArgs> UpdateCompleted;

        /// <summary>
        /// Raised when shutdown of this monitor completes.
        /// </summary>
        internal event EventHandler<SchedulerEventArgs> ShutdownCompleted;

        /// <summary>
        /// Raises the ItemRetrieved event.
        /// </summary>
        /// <param name="args">Event args</param>
        private void RaiseItemRetrieved(TaskCompletedEventArgs args)
        {
            Debug.Assert(args != null);

            EventHandler<TaskCompletedEventArgs> itemRetrieved = ItemRetrieved;

            if (itemRetrieved != null)
            {
                itemRetrieved(this, args);
            }
        }

        /// <summary>
        /// Raises the ItemDeleted event.
        /// </summary>
        /// <param name="args">Event arges</param>
        private void RaiseItemDeleted(MonitorItemDeletedEventArgs args)
        {
            Debug.Assert(args != null);

            EventHandler<MonitorItemDeletedEventArgs> itemDeleted = ItemDeleted;

            if (itemDeleted != null)
            {
                itemDeleted(this, args);
            }
        }
        
        /// <summary>
        /// Raises the UpdateCompleted event.
        /// </summary>
        /// <param name="args">Event args</param>
        private void RaiseUpdateCompleted(TaskCompletedEventArgs args)
        {
            Debug.Assert(args != null);

            EventHandler<TaskCompletedEventArgs> updateCompleted = UpdateCompleted;

            if (updateCompleted != null)
            {
                updateCompleted(this, args);
            }
        }

        /// <summary>
        /// Raises the ShutdownCompleted event.
        /// </summary>
        /// <param name="args">Event args</param>
        private void RaiseShutdownCompleted(SchedulerEventArgs args)
        {
            Debug.Assert(args != null);

            EventHandler<SchedulerEventArgs> shutdownCompleted = ShutdownCompleted;

            if (shutdownCompleted != null)
            {
                shutdownCompleted(this, args);
            }
        }

        /// <summary>
        /// Gets the specified mailbox item from EWS.
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="itemId">Item to be retrieved</param>
        protected abstract void GetItem(TaskContext taskContext, ItemIdType itemId);

        /// <summary>
        /// Handles completion of tasks to retrieve items from EWS.
        /// </summary>
        /// <param name="sender">Get item task</param>
        /// <param name="e">Get item task completed event args</param>
        protected void GetItem_Completed(object sender, TaskCompletedEventArgs e)
        {
            Debug.Assert(sender != null, "sender != null");
            Debug.Assert(e != null, "e != null");
            Debug.Assert(e.Context != null, "e.Context != null");

            Logger.WriteTraceVerbose(e.Context, "Monitor.GetItem_Completed");

            sender.AssertCast<GetItemTask>().Completed -= GetItem_Completed;

            RaiseItemRetrieved(e);
        }

        /// <summary>
        /// Handles deletion of a mailbox item of interest. 
        /// </summary>
        /// <param name="context">Logging context</param>
        /// <param name="itemId">Deleted item</param>
        /// <remarks>
        /// This method ensures that the deletion is handled on the serialized task thread.
        /// </remarks>
        protected void OnItemDeleted(Context context, ItemIdType itemId)
        {
            ExecuteDelegateTask executeDelegateTask = new ExecuteDelegateTask(new TaskContext(context), new TaskDelegate(OnItemDeleted), itemId);
            _taskSerializer.Execute(executeDelegateTask);
        }

        /// <summary>
        /// Handles deletion of a mailbox item of interest.
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="state">Deleted item</param>
        /// <remarks>
        /// This method ensures that the deletion is handled on the serialized task thread.
        /// </remarks>
        private void OnItemDeleted(TaskContext taskContext, object state)
        {
            RaiseItemDeleted(new MonitorItemDeletedEventArgs(taskContext, state.AssertCast<ItemIdType>()));
        }

        /// <summary>
        /// Handles completion of the update task. This method initiates fetching the items in the new search folder.
        /// </summary>
        /// <param name="sender">The update task</param>
        /// <param name="e">Event args</param>
        private void UpdateMonitorTask_Completed(object sender, TaskCompletedEventArgs e)
        {
            Debug.Assert(sender != null, "sender != null");
            Debug.Assert(e != null, "e != null");
            Debug.Assert(e.TaskContext != null, "e.TaskContext != null"); 
            Debug.Assert(e.Context != null, "e.Context != null");

            Logger.WriteTraceVerbose(e.Context, "Monitor.UpdateMonitorTask_Completed");

            sender.AssertCast<UpdateMonitorTask>().Completed -= UpdateMonitorTask_Completed;

            UpdateMonitorTaskCompletedEventArgs monitorTaskArgs = e.AssertCast<UpdateMonitorTaskCompletedEventArgs>();

            // Keep track of the resulting search folder regardless of errors. This is needed so that the old search folder can 
            // be deleted when a new search expression is specified or the service is stopped.
            _searchFolderId = monitorTaskArgs.SearchFolderId;

            if (e.Error == null)
            {
                if (_searchFolderId != null)
                {
                    // Only start the subscription timer if a search folder was created
                    StartSubscriptionTimer();
                }

                if (monitorTaskArgs.Items != null)
                {
                    // If there are items to be fetched, there must be a search folder
                    Debug.Assert(_searchFolderId != null, "_searchFolderId != null");

                    // Start feteching all the items in the new search folder
                    foreach (ItemIdType itemId in monitorTaskArgs.Items)
                    {
                        GetItem(e.TaskContext, itemId);
                    }
                }
            }

            RaiseUpdateCompleted(e);
        }

        /// <summary>
        /// Handles completion of the delete search folder task.
        /// </summary>
        /// <param name="sender">Shut down task</param>
        /// <param name="e">Event args</param>
        /// <remarks>
        /// The search folder is only explicitly deleted at shutdown so this method raises the shutdown completed event.
        /// </remarks>
        private void DeleteSearchFolderTask_Completed(object sender, TaskCompletedEventArgs e)
        {
            Debug.Assert(sender != null, "sender != null");
            Debug.Assert(e != null, "e != null");
            Debug.Assert(e.Context != null, "e.Context != null");

            Logger.WriteTraceVerbose(e.Context, "Monitor.DeleteSearchFolderTask_Completed");

            sender.AssertCast<DeleteFolderTask>().Completed -= DeleteSearchFolderTask_Completed;

            if (e.Error != null)
            {
                Logger.WriteTraceWarning(e.Context, "Failed to delete search folder during shutdown");
            }

            RaiseShutdownCompleted(new SchedulerEventArgs(e.Context));
        }

        /// <summary>
        /// Starts the subscription timer that ensures that the subscription is kept alive.
        /// </summary>
        private void StartSubscriptionTimer()
        {
            lock (_syncRoot)
            {
                if (_subscriptionTimer != null)
                {
                    _subscriptionTimer.Change(SubscriptionTimeout, Timeout.Infinite);
                }
            }
        }

        /// <summary>
        /// Stops the subscription timer.
        /// </summary>
        private void StopSubscriptionTimer()
        {
            lock (_syncRoot)
            {
                if (_subscriptionTimer != null)
                {
                    // Stop the timer by setting the timeout to infinite
                    _subscriptionTimer.Change(Timeout.Infinite, Timeout.Infinite);
                }
            }
        }

        /// <summary>
        /// Re-subscribes for notifications from EWS.
        /// </summary>
        /// <param name="state">Not used</param>
        /// <remarks>
        /// This method is invoked when there are no notifications from EWS within the timeout period. This method attempts 
        /// to remedy this problem by re-subscribing with EWS. This is done via the task serializer rather than the timer 
        /// callback thread to ensure that there aren't any synchronization issues.
        /// </remarks>
        private void SubscriptionTimerCallback(object state)
        {
            Context context = new ServerContext(_emailAddress);

            Logger.WriteTraceVerbose(context, "Monitor.SubscriptionTimerCallback");

            ExecuteDelegateTask resubscribeTask = new ExecuteDelegateTask(new TaskContext(context), Resubscribe, _searchFolderId);
            _taskSerializer.Execute(resubscribeTask);
        }

        /// <summary>
        /// Re-subscribes for notifications from EWS.
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="state">Search folder ID</param>
        /// <remarks>
        /// This method is executed by task serializer rather than the timer callback thread to ensure that there aren't 
        /// any synchronization issues.
        /// </remarks>
        private void Resubscribe(TaskContext taskContext, object state)
        {
            Debug.Assert(taskContext != null, "taskContext != null");
            Debug.Assert(taskContext.Context != null, "taskContext.Context != null");
            Debug.Assert(state != null, "state != null");

            Logger.WriteTraceVerbose(taskContext.Context, "Monitor.Resubscribe");

            if (_searchFolderId == null || _searchFolderId != state.AssertCast<BaseFolderIdType>())
            {
                // Only re-subscribe if there is a search folder and it hasn't changed since the timer expired. If the search 
                // folder changed, then it is safe to assume that the new search folder has a new subscription and the 
                // subscription timer has already been restarted.
                return;
            }

            Logger.WriteTraceWarning(taskContext.Context, "Re-subscribing for notifications from EWS");

            SubscribeTask subscribeTask = new SubscribeTask(taskContext, _client, _impersonation, _emailAddress, _searchFolderId, 
                _eventTypes, _subscriptionId, _watermark);
            subscribeTask.Completed += SubscribeTask_Completed;
            _taskSerializer.Execute(subscribeTask);
        }

        /// <summary>
        /// Handles re-subscription completing.
        /// </summary>
        /// <param name="sender">Subscription task</param>
        /// <param name="e">Event args</param>
        private void SubscribeTask_Completed(object sender, TaskCompletedEventArgs e)
        {
            Debug.Assert(sender != null, "sender != null");
            Debug.Assert(e != null, "e != null");
            Debug.Assert(e.Context != null, "e.Context != null");

            Logger.WriteTraceVerbose(e.Context, "Monitor.SubscribeTask_Completed");
            
            sender.AssertCast<SubscribeTask>().Completed -= SubscribeTask_Completed;

            // Regardless of the outcome, re-start the subscription timer
            StartSubscriptionTimer();
        }
    }
}
